import time
import wiringpi as gpio
import json
import os

class MotorError(Exception):
    def __init__(self,ErrorInfo,value = 0):
        super(MotorError,self).__init__(self)
        self.errorinfo = ErrorInfo
        self.value = value
    def __str__(self):
        return self.errorinfo

class MyPWM():
    """
        This class can be used to created a PWM wave for motor
        you need input your motor port to initialize method.
    """
    def __init__(self, plus_port=13):
        """
            plus_port: can output the PWM wave to drive motor to move (10mm/40000 or 1mm/4000)
        """
        self.plus_port = plus_port

    def set_cycle_info(self, micros, duty_cycle=0.5):
        """
            Sets the PWM frequency by specifying the cycle time in microseconds.
            The micros is supposed to be a unsigned int type data
            The minimum micros is supposed to be more than 10us

            Sets the duty cycle of this pwm
            the default duty_cycle is 0.5,which can determine the conduction time
        """
        self.micros = int(micros)
        self.duty_cycle = duty_cycle
        self.HighV_time = int(self.micros * self.duty_cycle)
        self.LowV_time = self.micros - self.HighV_time
         
    def start_plus(self,plus_number):
        """
            This function can creat plus_number implus which can drive motor to move
            The ideal function can drive motor to turn a circle(move 10mm) 
            Set the plus_number which can determine the Lead of your motor
            and it is same as the subdivision as your controller's seeting(sw5-sw8)
        """
        self.plus_number = int(plus_number)
        for i in range(0, self.plus_number):
            gpio.digitalWrite(self.plus_port, gpio.HIGH)
            gpio.delayMicroseconds(self.HighV_time)
            gpio.digitalWrite(self.plus_port, gpio.LOW)
            gpio.delayMicroseconds(self.LowV_time)


class Motor():
    """
        This class can drive motor to move the way we want. 
    """
    # This port is defined as BCM type
    gpio.wiringPiSetupGpio()

    def __init__(self,motro_port):
        """
            motro_port[0]=plus_port: can output the PWM wave to drive motor to move (10mm/40000 or 1mm/400)
            motro_port[1]=dir_port: can output the high level voltage to control motor move forward and low to move back
            motro_port[2]dir_port: the motor will stop working when this port output low level voltage
        """
        self.plus_port = motro_port[0]
        self.dir_port = motro_port[1]
        self.stop_port = motro_port[2]
        # set the needed port as output mode 
        gpio.pinMode(self.plus_port, gpio.OUTPUT)
        gpio.pinMode(self.dir_port, gpio.OUTPUT)
        gpio.pinMode(self.stop_port, gpio.OUTPUT)
        # set the dir_port and stop_port as High level voltage
        gpio.digitalWrite(self.dir_port, gpio.HIGH)
        gpio.digitalWrite(self.stop_port, gpio.HIGH)
        # creat a MyPWM instance to make pwm wave
        self.my_pwm = MyPWM(self.plus_port)
        self.my_pwm.set_cycle_info(10,0.5)
        # set the mortor length(0<position<100mm) as a private arrribute
        self.__length_MAX = 1000
        self.__length_MIN = 0
    
    def move_forward(self,motor_distance):
        """
            This function can drive the mortor to move forward when the position is lower than self.__length_MAX
            Attention: motor_distance > 0 
        """
        if (motor_distance < 0) or (motor_distance > self.__length_MAX) :
            return -1
        self.position = self.get_position()

        self.change_dir('+')

        if (self.position == self.__length_MAX):
            self.change_dir('-')
            raise MotorError('OverHigh!',self.position)
            move_status = False
        elif ((self.position + motor_distance) > self.__length_MAX):
            self.move_xmm(self.__length_MAX-self.position)
            self.position = self.__length_MAX
            self.change_dir('-')
            move_status = True
        else:
            self.move_xmm(motor_distance)
            self.position = self.position + motor_distance
            move_status = True

        self.update_position()

        return move_status

    def move_back(self,motor_distance):
        """
            This function can drive the mortor to move forward when the position is bigger than self.__length_MIN
            Attention: motor_distance > 0 
        """
        if (motor_distance < 0) or (motor_distance > self.__length_MAX) :
            return -1

        self.position = self.get_position()

        self.change_dir('-')

        if (self.position == self.__length_MIN):
            self.change_dir('+')
            raise MotorError('OverLow!',self.position)
            move_status = False
        elif ((self.position - motor_distance) < self.__length_MIN):
            self.move_xmm(self.position - self.__length_MIN)
            self.position = self.__length_MIN
            self.change_dir('+')
            move_status = True
        else:
            self.move_xmm(motor_distance)
            self.position = self.position - motor_distance
            move_status = True
        
        self.update_position()

        return move_status

    def move_2_original(self):
        """
            This function can help you drive the Slider eaily to be orignal status.
            Attention:you need to run 'stop_start(False)' to start again when you used this function 
        """
        self.position = self.get_position()
        status = self.move_back(self.position)
        self.stop_start(True)
        return status

    def move_xmm(self,x_mm):
        """
            This function can drive motor to move x mm
        """
        x_mm = float(x_mm)
        plus_number = (x_mm / 10.0) * 40000 
        self.my_pwm.start_plus(plus_number)

    def change_dir(self,dir_info):
        """
            This function can change the direction of the motor.
            '+':the motor will move forward
            '-':the motor will move back
        """
        if dir_info == '+':
            gpio.digitalWrite(self.dir_port, gpio.HIGH) 
        elif dir_info == '-':
            gpio.digitalWrite(self.dir_port, gpio.LOW)
        else:
            raise MotorError('Dir seeting error!')

    def stop_start(self,commd=False):
        """
            This function can stop motor work using 'commd = True',and the default commd is False
            and this function should be running first
        """
        self.motor_stop = commd
        if commd == False:
            gpio.digitalWrite(self.stop_port, gpio.HIGH)
            self.motor_status = 'start'
        else:
            gpio.digitalWrite(self.stop_port, gpio.LOW)
            self.motor_status = 'stop'

    def get_position(self):
        """
            Get the position from the 'current_position.json' to ensure the position can be same as fact
        """
        filename = 'current_position.json'
        with open(filename) as f_obj:
            position = json.load(f_obj)

        return position

    def update_position(self):
        """
            This function can update the current position and save it in 'current_position.json'
            We can use this method to synchronize the position, and keep pace with fact
        """
        filename = 'current_position.json'
        with open(filename, 'w') as f_obj:
            json.dump(self.position, f_obj)

    def set_position(self,position):
        """
            You can use this function to set the orignal position.
            It is supposed to be run once when you first use this programe
        """
        filename = 'current_position.json'
        with open(filename, 'w') as f_obj:
            json.dump(position, f_obj)





